In [1]:
import pandas as pd
from pandas import json_normalize

import datetime
import numpy as np
import json
import requests
import urllib.request

from datetime import datetime
import matplotlib.pyplot as plt
from scipy import interpolate
import statsmodels.api as sm
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.ticker import FuncFormatter
In [2]:
url =  "https://deribit.com/api/v2/public/get_instruments?currency=BTC&kind=option&expired=false"
with urllib.request.urlopen(url) as url:
    get_inst_names = json.loads(url.read().decode())

inst_names = pd.DataFrame(get_inst_names['result'])#.set_index('instrument_name')
inst_names['creation_date'] = pd.to_datetime(inst_names['creation_timestamp'], unit='ms')
inst_names['expiration_date'] = pd.to_datetime(inst_names['expiration_timestamp'], unit='ms')


api_data_list = []

for x in inst_names['instrument_name']:
    url = f'https://www.deribit.com/api/v2/public/get_order_book?instrument_name={x}&kind=option&expired=false'
    with urllib.request.urlopen(url) as response:
        api_data = json.loads(response.read().decode())

    api_data_df = pd.DataFrame([api_data])
    api_data_list.append(api_data_df)

all_data = pd.concat(api_data_list, ignore_index=True)
all_data = pd.json_normalize(all_data['result'])
In [4]:
data = all_data
exclude_columns = ['instrument_name', 'underlying_index']
data = data.apply(lambda x: pd.to_numeric(x, errors='ignore') if x.name not in exclude_columns else x)


strike = []
for index, i in enumerate(data['instrument_name']):
    i = float(i.split('-')[2])
    strike.append(i)
data["strikePx"] = strike

maturity = []
for index, i in enumerate(data['instrument_name']):
    i = i.split('-')[1]
    i = pd.to_datetime(i, unit ='ns')
    maturity.append(i)
data["maturity"] = maturity

days_to_maturity = []
for index, i in enumerate(data['maturity']):
    i = (i - datetime.today()).days
    days_to_maturity.append(i)
data["days_to_maturity"] = days_to_maturity


# Moneyness:  'underlying price' is the price of underlying Futures
data['moneyness'] = data['strikePx'].astype(float)/ data['underlying_price'].astype(float)

#log Moneyness, making extreme values less extreme
#data['moneyness'] = np.log(data['strikePx'].astype(float)/ data['fwdPx'].astype(float)) 


data.loc[data['instrument_name'].str.contains('-P'), 'moneyness'] = data.loc[data['instrument_name'].str.contains('-P'), 'moneyness'] * -1

data = data.sort_values(['days_to_maturity','strikePx']).query('days_to_maturity > 0')
all_calls = data[data['instrument_name'].str.contains('-C')].sort_values(['days_to_maturity', 'strikePx']).query('days_to_maturity > 0')
all_puts = data[data['instrument_name'].str.contains('-P')].sort_values(['days_to_maturity', 'strikePx']).query('days_to_maturity > 0')
In [5]:
#%matplotlib notebook 

# Interpolate implied volatility using a cubic spline
'''
Cubic Spline interpolation uses cubic polynominals to interpolate between data points

'''

def plot_iv_surf(x,y,z,x2=None,y2=None,z2=None,label=''):
    fig = plt.figure(3, figsize=(6,6))
    ax=plt.axes(projection='3d')
    ax.set_title('BTC Implied Volatility Surface, OKX')
    ax.set_zlabel('Implied Volatility')
    plt.xlabel('Strike / FuturesPx')
    plt.ylabel('Days To Maturity')
    #ax.zaxis.set_major_formatter(FuncFormatter(lambda z, _: '{:.0%}'.format(z)))
    if z2 is not None:
        ax.scatter3D(x2,y2,z2, c='r', s=100,label=label)
    ax.plot_surface(x, y, z, rstride=1, cstride=1,alpha=0.5)
    ax.legend()
    

x = data['moneyness']
y = data['days_to_maturity']
z = data['mark_iv']/100

X,Y = np.meshgrid(np.linspace(.95,1.05,99),np.linspace(1,np.max(y),100))
Z = interpolate.griddata(np.array([x,y]).T,np.array(z),(X,Y), method='cubic')

xyz = pd.DataFrame({'x':x,'y':y,'z':z})
xyz = xyz.query('x>0.95 & x<1.05')
plot_iv_surf(X,Y,Z,xyz['x'],xyz['y'],xyz['z'],'Observed IV')

iv_df = pd.DataFrame(Z, index=np.linspace(10,np.max(y),100), columns=np.linspace(.95,1.05,99))
No description has been provided for this image
In [6]:
import plotly.graph_objects as go

def plot_iv_surf_plotly(x, y, z, x2=None, y2=None, z2=None, label=''):
    fig = go.Figure()

    # Create the surface plot
    fig.add_trace(go.Surface(x=x, y=y, z=z, colorscale='Viridis', opacity=0.7))

    # Add scatter points if provided
    if x2 is not None and y2 is not None and z2 is not None:
        fig.add_trace(go.Scatter3d(
            x=x2,
            y=y2,
            z=z2,
            mode='markers',
            marker=dict(size=5, color='red', symbol='circle'),
            name=label
        ))

    fig.update_layout(
        title='BTC Implied Volatility Surface, OKX',
        scene=dict(
            xaxis_title='Strike / FuturesPx',
            yaxis_title='Days To Maturity',
            zaxis_title='Implied Volatility',
            zaxis=dict(tickformat=".0%")
        ),
        legend=dict(x=0, y=1),
        width=1200,  # Set the width of the plot
        height=800   # Set the height of the plot
    )

    fig.show()

# Interpolate implied volatility using a cubic spline
x = data['moneyness']
y = data['days_to_maturity']
z = data['mark_iv'] / 100

X, Y = np.meshgrid(np.linspace(.95, 1.05, 99), np.linspace(1, np.max(y), 100))
Z = interpolate.griddata(np.array([x, y]).T, np.array(z), (X, Y), method='cubic')

xyz = pd.DataFrame({'x': x, 'y': y, 'z': z})
xyz = xyz.query('x > 0.95 & x < 1.05')

plot_iv_surf_plotly(X, Y, Z, xyz['x'], xyz['y'], xyz['z'], 'Observed IV')

iv_df = pd.DataFrame(Z, index=np.linspace(10, np.max(y), 100), columns=np.linspace(.95, 1.05, 99))
In [ ]: